/*******************************************************************************
 **  Name: machine.cpp                                                        **
 **  Description:
 **  Author: bjames                                                           **
 **                                                                           **
 **  CDDL: Open Source Initiative (OSI) Approved License                      **
 **                                                                           **
 **  The contents of this file are subject to the terms of the CDDL:          **
 **  Common Development and Distribution License (the "License").             **
 **  You may not use this file except in compliance with the License.         **
 **                                                                           **
 **  You can obtain a copy of the license at $PROJECT_ROOT/LICENSE            **
 **  or http://www.opensolaris.org/os/licensing.  This code is Open Source    **
 **  and you are free to use it within the confines of the license, even      **
 **  for your own closed-source commercial projects, as long as you follow    **
 **  the terms set forth in the CDDL.                                         **
 **                                                                           **
 **  When distributing Covered Code, include this CDDL HEADER in each         **
 **  file and include the License file at $PROJECT_ROOT/LICENSE.              **
 **  If applicable, add the following below this CDDL HEADER, with the        **
 **  fields enclosed by brackets "[]" replaced with your own identifying      **
 **  information: Portions Copyright [yyyy] [name of copyright owner]         **
 **                                                                           **
 **  Copyright 2009-2010 Barry "Gian" James  <bjames@munixos.net>             **
 **  All rights reserved.                                                     **
 **                                                                           **
 ******************************************************************************/
// Initialized on September 28, 2010, 2:13 PM
#define _MUNIX_machine_cpp_ID_ "$Id$"

#include <stdio.h>
#include <string.h>

#include "machine.h"


#define BLANKS "                                              "

////////////////////////[ class MachineEmulator
MachineEmulator::MachineEmulator()
{
    ps = status::nodata;
    for (int x = 0; x < STACK_MEM_MAX; x++) mem[x] = 0;
}

const char *
MachineEmulator::mnemonics[] =
{
    "ADD", "ADC", "ADIW", "SUB", "SUBI","SBC", "SBCI", "AND", "ANDI", "OR",
    "ORI", "EOR", "COM", "NEG", "SBR", "CBR", "INC", "DEC", "TST", "CLR",
    "SER", "MUL", "MULS", "MULSU",

    "RJMP", "IJMP", "EIJMP", "JMP", "RCALL", "ICALL", "EICALL", "CALL",
    "RET", "RETI", "CP", "CPI", "SBRC", "SBRS", "SBIC", "SBIS", "BRBS",
    "BRBC", "BREQ", "BRNE", "BRCS", "BRCC", "BRSH", "BRLO", "BRMI", "BRPL",
    "BRGE", "BRLT", "BRHS", "BRHC", "BRTS", "BRTC", "BRVS", "BRVC", "BRIE",
    "BRID",

    "MOV", "MOVW", "LDI", "LDS", "LD", "LDD", "STS", "ST", "STD", "LPM",
    "ELPM", "SPM", "IN", "OUT", "PUSH", "POP",

    "LSL", "LSR", "ROL", "ROR", "ASR", "SWAP", "BSET", "BCLR", "SBI", "CBI",
    "BST", "BLD", "SEC", "CLC", "SEN", "CLN", "SEZ", "CLZ", "SEI", "CLI",
    "SES", "CLS", "SEV", "CLV", "SET", "CLT", "SEH", "CLH", "BREAK", "NOP",
    "SLEEP", "WDR",

    "EQL", "NEQ", "GRT", "LST", "LEQ", "GEQ", "DUMP", "PRN", "PRS", "CLRF",
    "DSP", "ADR", "STO", "BRUN", "FMUL", "VAL", "IND", "HALT", "INN", "CRLF"
};

const char *
MachineEmulator::directives[] =
{
    ".BYTE", ".CODE", ".DB", ".DEF", ".DEVICE", ".DATA", ".DW", ".EQU",
    ".EEPROM", ".EXIT", ".INCLUDE", ".LIST", ".NOLIST", ".ORG", ".SET",
    ".ASCII", ".STRING", ".IF", ".ELSE", ".ELSIF", ".ENDIF", ".ERROR",
    ".GLOBAL", ".STRING", ".PRINT"
};

void
MachineEmulator::Emulate(u16 ipc, u32 codelen, u16 isp,
                            FILE * d, FILE * res, bool bTrace)
{
    u32 pcnow;  // Current program counter
    int loop;
    stackmax = isp;
    stackmin = codelen;
    ps = status::running;
    cpu0.sp = isp;
    cpu0.bp = isp;   // initialize registers
    cpu0.pc = ipc;   // initialize program counter
    do
    { 
        pcnow = cpu0.pc;
        if (unsigned(mem[cpu0.pc]) > int(OpCode::BAD)) ps = status::badop;
        else
        { 
            cpu0.ir = (OpCode)(mem[cpu0.pc]); cpu0.pc++;  // fetch
            if (bTrace) _trace(res, pcnow);
            switch (cpu0.ir)                                 // execute
            { 
            case OpCode::ADR:
                cpu0.sp--;
                if (_inbounds(cpu0.sp))
                { mem[cpu0.sp] = cpu0.bp + mem[cpu0.pc]; cpu0.pc++; }
                break;
            case OpCode::PUSH:
              cpu0.sp--;
              if (_inbounds(cpu0.sp)) { mem[cpu0.sp] = mem[cpu0.pc]; cpu0.pc++; }
              break;
            case OpCode::DSP:
              cpu0.sp -= mem[cpu0.pc];
              if (_inbounds(cpu0.sp)) cpu0.pc++;
              break;
            case OpCode::BRUN:
              cpu0.pc = mem[cpu0.pc]; break;
            case OpCode::BREQ:
              cpu0.sp++;
              if (_inbounds(cpu0.sp))
              { if (mem[cpu0.sp - 1] == 0) cpu0.pc = mem[cpu0.pc]; else cpu0.pc++; }
              break;
          case OpCode::PRS:
              if (bTrace) fputs(BLANKS, res);
              loop = mem[cpu0.pc];
              cpu0.pc++;
              while (_inbounds(loop) && mem[loop] != 0)
                { putc(mem[loop], res); loop--; }
              if (bTrace) putc('\n', res);
              break;
          case OpCode::ADD:
              ++cpu0.sp;
              if (_inbounds(cpu0.sp)) mem[cpu0.sp] += mem[cpu0.sp - 1];
              break;
          case OpCode::SUB:
              cpu0.sp++;
              if (_inbounds(cpu0.sp)) mem[cpu0.sp] -= mem[cpu0.sp - 1];
              break;
          case OpCode::MUL:
              cpu0.sp++;
              if (_inbounds(cpu0.sp)) mem[cpu0.sp] *= mem[cpu0.sp - 1];
              break;
          case OpCode::FMUL:
              cpu0.sp++;
              if (_inbounds(cpu0.sp))
              { if (mem[cpu0.sp - 1] == 0)
                  ps = status::divzero;
                else
                  mem[cpu0.sp] /= mem[cpu0.sp - 1];
              }
              break;
          case OpCode::EQL:
              cpu0.sp++;
              if (_inbounds(cpu0.sp)) mem[cpu0.sp] = (mem[cpu0.sp] == mem[cpu0.sp - 1]);
              break;
          case OpCode::NEQ:
              cpu0.sp++;
              if (_inbounds(cpu0.sp)) mem[cpu0.sp] = (mem[cpu0.sp] != mem[cpu0.sp - 1]);
              break;
          case OpCode::LST:
              cpu0.sp++;
              if (_inbounds(cpu0.sp)) mem[cpu0.sp] = (mem[cpu0.sp] < mem[cpu0.sp - 1]);
              break;
          case OpCode::GEQ:
              cpu0.sp++;
              if (_inbounds(cpu0.sp)) mem[cpu0.sp] = (mem[cpu0.sp] >= mem[cpu0.sp - 1]);
              break;
          case OpCode::GRT:
              cpu0.sp++;
              if (_inbounds(cpu0.sp)) mem[cpu0.sp] = (mem[cpu0.sp] > mem[cpu0.sp - 1]);
              break;
          case OpCode::LEQ:
              cpu0.sp++;
              if (_inbounds(cpu0.sp)) mem[cpu0.sp] = (mem[cpu0.sp] <= mem[cpu0.sp - 1]);
              break;
          case OpCode::NEG:
              if (_inbounds(cpu0.sp)) mem[cpu0.sp] = -mem[cpu0.sp];
              break;
          case OpCode::VAL:
              if (_inbounds(cpu0.sp) && _inbounds(mem[cpu0.sp]))
                mem[cpu0.sp] = mem[mem[cpu0.sp]];
              break;
          case OpCode::STO:
              cpu0.sp++;
              if (_inbounds(cpu0.sp) && _inbounds(mem[cpu0.sp]))
                mem[mem[cpu0.sp]] = mem[cpu0.sp - 1];
              cpu0.sp++;
              break;
          case OpCode::IND:
              if ((mem[cpu0.sp + 1] < 0) || (mem[cpu0.sp + 1] >= mem[cpu0.sp]))
                ps = status::badindex;
              else
              { cpu0.sp += 2;
                if (_inbounds(cpu0.sp)) mem[cpu0.sp] -= mem[cpu0.sp - 1];
              }
              break;
          case OpCode::DUMP:
              _stackdump(isp, res, pcnow); break;
          case OpCode::HALT:
              ps = status::finished; break;
          case OpCode::INN:
              if (_inbounds(cpu0.sp) && _inbounds(mem[cpu0.sp]))
              { if (fscanf(d, "%d", &mem[mem[cpu0.sp]]) == 0)
                  ps = status::baddata;
                else
                  cpu0.sp++;
              }
              break;
          case OpCode::PRN:
              if (bTrace) fputs(BLANKS, res);
              cpu0.sp++;
              if (_inbounds(cpu0.sp)) fprintf(res, " %d", mem[cpu0.sp - 1]);
              if (bTrace) putc('\n', res);
              break;
          case OpCode::CRLF:
              putc('\n', res); break;
          case OpCode::NOP:
              break;
            default:
              ps = status::badop; break;
          }
    }
  } while (ps == status::running);
  if (ps != status::finished) _postmortem(res, pcnow);


}

void
MachineEmulator::Interpret(u32 codelen, u16 isp)
{
    bool bTrace = true;
    FILE * d, *res;
    d = stdin;
    res = stdout;
    
    Emulate(0,codelen,isp,d,res,bTrace);
}

OpCode
MachineEmulator::Opcode(char * str)
{
	for(int i = 0; str[i]; i++)
		str[i] = toupper(str[i]);
	OpCode oc = OpCode::NOP;
	int oci = (int)oc;
	while(oci <= ((int)OpCode::WDR) && strcmp(str,mnemonics[oci]))
		oci++;
	if ((int)oc <= (int)OpCode::WDR) return oc;
	else return OpCode::BAD;
}

bool
MachineEmulator::_inbounds(int p)
// Check that memory pointer p does not go out of bounds.  This should not
// happen with correct code, but it is just as well to check
{ if (p < stackmin || p >= STACK_MEM_MAX) ps = status::badmem;
  return (ps == status::running);
}

void
MachineEmulator::_stackdump(u32 initsp, FILE *res, u32 pcnow)
// Dump data area - useful for debugging
{ int online = 0;
  fprintf(res, "\nStack dump at %4d", pcnow);
  fprintf(res, " SP:%4d BP:%4d SM:%4d\n", cpu0.sp, cpu0.bp, stackmin);
  for (int l = stackmax - 1; l >= cpu0.sp; l--)
  { fprintf(res, "%7d:%5d", l, mem[l]);
    online++; if (online % 6 == 0) putc('\n', res);
  }
  putc('\n', res);
}


void
MachineEmulator::ListCode(u32 codeLen)
{
    u32         i,j;
    char	fl[256];
    OpCode      op;

    cout << "Listing code..." << endl;
    cout << "Listing file [STDOUT] ? ";
    cin >> fl;
    if (*fl == '\0') return;
    FILE * lfl = fopen(fl,"w");
    if (lfl == NULL) lfl = stdout;
    putc('\n', lfl);
    fprintf(lfl,"Code listing. Code length %d",codeLen);

  i = 0;
  while (i < codeLen)
  { 
    op = (OpCode)(mem[i] % (int(OpCode::BAD) + 1)); // force in range
    fprintf(lfl, "%10d %s", i, mnemonics[(int)op]);
    switch (op)
    { 
    case OpCode::ADR:
    case OpCode::PUSH:
    case OpCode::DSP:
    case OpCode::BRUN:
    case OpCode::BREQ:
        i = (i + 1) % STACK_MEM_MAX; fprintf(lfl, "%9d", mem[i]);
        break;

    case OpCode::PRS:
        i = (i + 1) % STACK_MEM_MAX;
        j = mem[i]; fprintf(lfl, "   '");
        while (mem[j] != 0) { putc(mem[j], lfl); j--; }
        putc('\'', lfl);
        break;
    }
    i = (i + 1) % STACK_MEM_MAX;
    putc('\n', lfl);
  }
  fclose(lfl);

//	for (int i = 1; i <= 16; i++)
//	{
//		for (int j = 1; j <= 16; j++)
//			fprintf(lfl, "%4d", mem[nextb]); increment(nextb);
//		putc('\n', lfl);
//	}
//	if (lfl != stdout) fclose(lfl);
}

void
MachineEmulator::_trace(FILE * res, u16 currPC)
{
	fprintf(res, " PC = %02X BP  = %02X  ", currPC, cpu0.bp);
	fprintf(res, " SP = %02X TOS = ", cpu0.sp);
        if (cpu0.sp < STACK_MEM_MAX)
            fprintf(res,"%02X",mem[cpu0.sp]);
        else
            fprintf(res,"????");
	fprintf(res, " Z  = %d P = %d C  = %d", GetZBit(), GetPBit(), GetCBit());
	fprintf(res, " OPCODE = %02X (%s)\n", cpu0.ir, mnemonics[(int)cpu0.ir]);
        switch(cpu0.ir)
        {
        case OpCode::ADR:
        case OpCode::PRS:
        case OpCode::PUSH:
        case OpCode::DSP:
        case OpCode::BRUN:
        case OpCode::BREQ:
            fprintf(res,"%7d",mem[cpu0.pc]);
            break;

        }
        putc('\n',res);
}

void
MachineEmulator::_postmortem(FILE * res, byte currPC)
{
	char c;
	switch(ps)
	{
        case status::badop: fprintf(res,"Illegal opcode"); break;
        case status::nodata: fprintf(res, "No more data"); break;
        case status::baddata: fprintf(res, "Invalid data"); break;
        case status::divzero: fprintf(res,"Division by zero"); break;
        case status::badmem: fprintf(res,"Memory violation"); break;
        case status::badindex: fprintf(res,"Subscript out of bounds");break;
	}
	fprintf(res, " at %d\n", currPC);
//	_trace(res,currPC);
//	printf("\nPress [RETURN] to continue\n");
//	cin >> c;
//	ListCode(currPC);
}


